package org.javahispano.javacup.tacticas_aceptadas.chr.conwuzor.accion;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.StringTokenizer;

import org.javahispano.javacup.modelo.ComandoGolpearBalon;
import org.javahispano.javacup.modelo.ComandoIrA;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;

import org.javahispano.javacup.tacticas_aceptadas.chr.conwuzor.tactica.utilidades.Entorno;
import org.javahispano.javacup.tacticas_aceptadas.chr.conwuzor.tactica.utilidades.Jugador;
import org.javahispano.javacup.tacticas_aceptadas.chr.conwuzor.tactica.utilidades.Rival;

/**
 * Clase utilizada para generar todas las acciones que puede realizar el jugador que se encuentra en contacto con 
 * el bal�n.
 * 
 * @author Christian Onwuzor Mart�n (chr -> airchris01@yahoo.es)
 */
public class Generador {

    // M�todo que calcula la Accion avanzar de un jugador.
    // Si no puede avanzar devuelve un null.
    public AccionAvanzar avanzar(SituacionPartido sp, Jugador jugador, Rival[] arrayRivales) {

    	// Variable en la que devolveremos el resultado.
    	AccionAvanzar accion = null;
        
        // Calculamos los par�metros necesarios para realizar el avance mediante el comando golpear bal�n.
        double fuerza = jugador.getFuerzaGolpeoAvanzar();
        double anguloVertical = 0;
        double angulo;

        // Si el jugador ha superado el l�mite de avance sobre el eje Y, avanzamos
        // hacia la porter�a
        if (sp.misJugadores()[jugador.getIndice()].getY() > Entorno.LIMITE_AVANCE_Y) {
        	
        	// Definimos la Y del avance.
        	double y = Constantes.LARGO_CAMPO_JUEGO /2;
        	
        	// Si nuestra ubicaci�n es pr�xima a la l�nea de fondo y nos encontramos fuera del eje X de la 
        	// porter�a, reducimos la Y del avance para abrir el hueco.
        	if (sp.balon().getY() > ((Constantes.LARGO_CAMPO_JUEGO /2) - 15) &&
        		Math.abs(sp.balon().getX()) > Constantes.posteDerArcoSup.getX()) {
        		
        		y =  (Constantes.LARGO_CAMPO_JUEGO /2) - 1.5; 
        	}
        	
        	if (jugador.getIndice() == 8 || jugador.getIndice() == 9) {
        		
	        	// Si el jugador se encuentra a la izquierda se dirige hacia el poste izquierdo para abrir m�s hueco.
	        	if (Entorno.alineacionNormal[jugador.getIndice()].getX() < 0) {
	        		angulo = Math.toDegrees(sp.misJugadores()[jugador.getIndice()].angulo(
	        					new Posicion(Constantes.posteIzqArcoSup.getX()-1.5, y)));
	        	}
	        	// Si el jugador se encuentra a la derecha se dirige al poste derecho.
	        	else {
	        		angulo = Math.toDegrees(sp.misJugadores()[jugador.getIndice()].angulo(
	        					new Posicion(Constantes.posteDerArcoSup.getX()+1.5, y)));
	        	}
        	}
        	else {
        		angulo = Math.toDegrees(sp.misJugadores()[jugador.getIndice()].angulo(
        					new Posicion(Constantes.centroArcoSup.getX(), y)));
        	}
        }
        // Si no ha superado el l�mite avanzamos sobre el eje Y.
        else {
            angulo = 90.0;
        }
        
        // Dado que vamos a avanzar, solamente nos interesa calcular si el jugador que avanza llega antes al bal�n
        // que los rivales.
        Jugador[] arrayJugadores = new Jugador[] {jugador};
        boolean puedoAvanzar = alcanzoAntesBalon(sp, jugador, false, 
        										 arrayJugadores, arrayRivales,  
        										 angulo, anguloVertical, fuerza);
        
        
        // Si no podemos avanzar en el �ngulo calculado, buscamos avanzar en un �ngulo
        // de + - una variacion establecida.
        if (!puedoAvanzar) {

            Double anguloRango = avanzarRango (sp, jugador, false, arrayJugadores, arrayRivales, 
        									   30, anguloVertical, fuerza);
            
            // Si hemos obtenido un �ngulo de avance lo indicamos para permitir realizar la acci�n.  
            if (anguloRango != null) {
            	
            	puedoAvanzar = true;
            	angulo = anguloRango;
            }
        }

        
        // Si podemos avanzar, instanciamos la acci�n adecuada.
        if (puedoAvanzar) {

            accion = new AccionAvanzar(new ComandoGolpearBalon(
                    							jugador.getIndice(),
                    							angulo,
                    							fuerza,
                    							anguloVertical),
                    					new ComandoIrA(jugador.getIndice(),
                    								   sp.misJugadores()[jugador.getIndice()].moverAngulo(angulo, 50)));
            
            
        }

        return accion;
    }

    
    // M�todo que calcula la acci�n de pase del jugador a todos los jugadores que
    // pueda enviarles la pelota. Devuelve un listado de arrays en los que en la
    // primera posici�n se sencuentra el Jugador al que le env�a la pelota y en la
    // segunda posici�n la acci�n a realizar.
    public AccionPasar pasar(SituacionPartido sp, Jugador jugador,
                             Jugador[] arrayJugadores, Rival[] arrayRivales) {
    	
    	AccionPasar accionPasar = null;
    	
    	// Calculamos si nos encontramos en una acci�n ofensiva para cargar la alineaci�n ofensiva.
    	boolean accionOfensiva = sp.balon().getY() > 10;
    	String pases = accionOfensiva ? Entorno.valoracionAtaque[jugador.getIndice()] : 
    									Entorno.valoracionNormal[jugador.getIndice()];
    	
    	// Variables que utilizaremos en el caso de estar en una acci�n ofensiva para calcular el mejor pase.
    	Jugador jugadorAsignado = null;
    	double anguloXYAsignado = 0;
    	double anguloZAsignado = 0;
    	double fuerzaAsignado = 0;
    	boolean puertaVaciaAsignado = false;
    	
    	// Recorremos todos los jugadores a los que podemos enviarles la pelota por estricto orden de preferencia.
    	StringTokenizer st = new StringTokenizer(pases, "|");
    	while (st.hasMoreTokens() && accionPasar == null) {
    	
    		String destinoPase = st.nextToken();
    		if (destinoPase != null && destinoPase.length() > 0) {
    			
    			Jugador jugadorDestino = arrayJugadores[Integer.parseInt(destinoPase)];
    			
    			if (jugadorDestino.getIndice() != jugador.getIndice() &&
    				cumpleCondicionesPase(sp, jugador, jugadorDestino)) {
    			
					// Calculamos los par�metros necesarios para llamar al m�todo que calcula si el pase llegar�
					// a su destino.
	                double fuerza;
	                double anguloZ;
	                double anguloXY = Math.toDegrees(sp.misJugadores()[jugador.getIndice()].
	                									angulo(sp.misJugadores()[jugadorDestino.getIndice()]));
	
	                // Obtenemos la distancia del pase.
	                double distanciaPase = sp.misJugadores()[jugador.getIndice()].distancia(
	                                            sp.misJugadores()[jugadorDestino.getIndice()]);
	
	                // Llegamos con un paso raso
	                if (distanciaPase < jugador.getDistanciaGolpeoBajo()) {
	                    anguloZ = 0;
	                    
	                    // Si la distancia con un pase raso es mayor que la mitad de la distancia que alcanza 
	                    // un jugador, le aplicamos la fuerza m�xima para que llegue antes.
	                    if (distanciaPase >= jugador.getDistanciaGolpeoBajo() / 2 ||
	                    	destinoDentroArea(sp, jugadorDestino)) {
	                    	
	                    	fuerza = Entorno.FUERZA_MAXIMA_GOLPEO;
	                    }
	                    // En caso contrario, le asignamos la fuerza justa.
	                    else {
	                    	fuerza = (Entorno.FUERZA_MAXIMA_GOLPEO * distanciaPase) / jugador.getDistanciaGolpeoBajo();
	                    }
	                }
	                else {
	                    anguloZ = 30;
	                    fuerza = ((Entorno.FUERZA_MAXIMA_GOLPEO * distanciaPase) / jugador.getDistanciaGolpeoAlto()) + 0.2;
	                }
	                
	                
	                // Si el pase llega a su destino o a un compa�ero de equipo, lo realizamos.
					if (alcanzoAntesBalon(sp, jugador, false, 
							  			  arrayJugadores, arrayRivales,  
							  			  anguloXY, anguloZ, fuerza)) {
	
						// Si no estamos en una acci�n ofensiva devolvemos el primer pase encontrado.
						if (!accionOfensiva) {
						
							accionPasar = new AccionPasar(new ComandoGolpearBalon(
																  				  jugador.getIndice(),
																  				  anguloXY,
																  				  fuerza,
																  				  anguloZ),
														  jugadorDestino);
						}
						// Si nos encontramos en una acci�n ofensiva calculamos el mejor pase.
						else {
							
							if (jugadorAsignado != null) {
								boolean puertaVaciaActual = puertaVacia(sp, sp.misJugadores()[jugadorDestino.getIndice()]);

								// El destino consultado tiene tiro a puerta vacia y el anterior no. 
								if (puertaVaciaActual && !puertaVaciaAsignado) {
									
									jugadorAsignado = jugadorDestino;
							    	anguloXYAsignado = anguloXY;
							    	anguloZAsignado = anguloZ;
							    	fuerzaAsignado = fuerza;
							    	puertaVaciaAsignado = puertaVaciaActual;
								}
								// Los 2 tienen tiro a puerta vacia o ninguno de los 2 lo tiene.
								else {
									
									// Me quedo con el que este m�s cerca del centro de la porter�a.
									double distanciaAnterior = sp.misJugadores()[jugadorAsignado.getIndice()].distancia(Constantes.centroArcoSup);
									double distanciaActual = sp.misJugadores()[jugadorDestino.getIndice()].distancia(Constantes.centroArcoSup);
									if (distanciaActual < distanciaAnterior) {

										jugadorAsignado = jugadorDestino;
								    	anguloXYAsignado = anguloXY;
								    	anguloZAsignado = anguloZ;
								    	fuerzaAsignado = fuerza;
								    	puertaVaciaAsignado = puertaVacia(sp, sp.misJugadores()[jugadorDestino.getIndice()]);
									}
								}
							}
							// Si no habia anteriormente un jugador asignado le asignamos los datos del actual. 
							else {
								jugadorAsignado = jugadorDestino;
						    	anguloXYAsignado = anguloXY;
						    	anguloZAsignado = anguloZ;
						    	fuerzaAsignado = fuerza;
						    	puertaVaciaAsignado = puertaVacia(sp, sp.misJugadores()[jugadorDestino.getIndice()]);
							}
						}
					}
    			}
			}
    	}
    	
    	// Si nos encontramos en una accion ofensiva y hemos obtenido un destino, generamos la acci�n del pase.
    	if (accionOfensiva && jugadorAsignado != null) {
    		
    		accionPasar = new AccionPasar(new ComandoGolpearBalon(
	  				  											  jugador.getIndice(),
	  				  											  anguloXYAsignado,
	  				  											  fuerzaAsignado,
	  				  											  anguloZAsignado),
	  				  					  jugadorAsignado);
    	}
    	
        return accionPasar;
    }    
    
    
    // M�todo que calcula la acci�n de tirar.
    public AccionTirar tirar(SituacionPartido sp, Jugador jugador, boolean calculoParaJugadorPosesion) {

    	AccionTirar accion = null;

        // Solamente tiramos si el tiro llega a porter�a y nos encontramos dentro
        // de la distancia designada para tirar.
        double distanciaPorteria = sp.misJugadores()[jugador.getIndice()].distancia(Constantes.centroArcoSup);

        boolean tiroPuertaVacia = false;
        boolean tiroSeguro = false;
        
        // Si el calculo es para el jugador que tiene la posesi�n el inicio del tiro lo cogemos desde la posici�n
        // actual del bal�n, para aumentar la precisi�n, en caso contrario cogemos la posici�n del jugador que
        // realiza la acci�n ya que estaremos buscando la acci�n de cualquier jugador.
        Posicion inicioTiro = calculoParaJugadorPosesion ? sp.balon() : sp.misJugadores()[jugador.getIndice()];
        
        if ( jugador.getDistanciaGolpeoAlto() > distanciaPorteria && 
        	 (tiroPuertaVacia || distanciaPorteria < Entorno.DISTANCIA_MINIMA_TIRO))
        {
        	
        	Hashtable<String, Object> tabla = null;
        	
        	
        	// Realizamos una primera pasada calculando si podemos realizar un tiro seguro.
        	tabla = calculaPosicionTiro(sp, jugador, inicioTiro, true);
    		if (tabla != null) {
    			tiroSeguro = true;
    		}
    		
    		
        	// Si no hemos encontrado un tiro seguro realizamos una segunda pasada con el m�todo habitual.
        	if (tabla == null) {
        		
        		tabla = calculaPosicionTiro(sp, jugador, inicioTiro, false);
        		tiroPuertaVacia = puertaVacia(sp, sp.balon());
        	}
        	
        	
            if (tabla != null) {

            	accion = new AccionTirar(new ComandoGolpearBalon(jugador.getIndice(),
                                                  				   (Posicion)tabla.get("posicion"),
                                                  				   Entorno.FUERZA_MAXIMA_GOLPEO,
                                                  				   jugador.getAnguloTiro()),
                                         forzadoTiro(sp),
                                         (Double)tabla.get("variacion"),
                                         tiroPuertaVacia,
                                         tiroSeguro);
            }
        }

        return accion;
    }
    
    
    // M�todo que calcula la acci�n de despeje.
    public AccionDespejar despejar(SituacionPartido sp, Jugador jugador, Rival[] arrayRivales) {

    	// Variable en la que devolveremos el resultado.
    	AccionDespejar despejar = null;
    	boolean realizaDespeje = false;

    	double anguloVertical = 45;
    	double fuerza = Entorno.FUERZA_MAXIMA_GOLPEO;
    	double amplitudAngular = 55;

    	double incremento = (sp.misJugadores()[jugador.getIndice()].getX() < 0) ? 5 : -5;
    	double anguloInicial = 90;
    	double angulo = anguloInicial;
    	
    	boolean salir = false;
    	boolean otroLado = false;
    	long contador = 0;
    	
    	
        do {
        	realizaDespeje = alcanzoAlturaDespeje(sp, jugador, arrayRivales, angulo, anguloVertical, fuerza);

    		// Si no podemos realizar el despeje para los datos obtenidos.
    		if (!realizaDespeje) {
    			
    			// Si no hemos superado la amplitud angular, realizamos un nuevo incremento. 
    			if ((Math.abs(incremento) * contador)  < amplitudAngular) {
    				
    				// Si tenemos que buscar el �ngulo equivalente al otro lado.
    				if (otroLado) {
    					angulo = anguloInicial - (incremento * contador);
    				}
    				// Si no tenemos que buscar el �ngulo al otro lado.
    				else {
    					
    		    		// Aumemtamos el contador de la iteraci�n.
    		    		contador ++;
    		    		
    					angulo = (incremento * contador) + anguloInicial;
    				}
    			}
    			else {
    				salir = true;
    			}
    		}
    		
    		// Cambiamos el valor de la variable que controla si hemos buscado el �ngulo del otro lado.
    		otroLado = !otroLado;
    		
    	} while (!realizaDespeje && !salir);
    	
    	
    	
    	// Si hemos encontrado un �ngulo de despeje, realizamos el despeje.
    	if (realizaDespeje) {
    		despejar = new AccionDespejar(new ComandoGolpearBalon(jugador.getIndice(),
                    					  				  		  angulo,
                    					  				  		  fuerza,
                    					  				  		  anguloVertical));
    	}
    	// En caso contrario despejamos hacia el centro de la porter�a.
    	else {
    		despejar = new AccionDespejar(new ComandoGolpearBalon(jugador.getIndice(),
      					  				  				  		  Constantes.centroArcoSup,
      					  				  				  		  fuerza,
      					  				  				  		  60));
    	}
				
        return despejar;
    }
    
    
    
    // M�todo que calcula acci�n de realizar un regate.
    public AccionRegatear regatear(SituacionPartido sp, Jugador jugador, Rival[] arrayRivales) {

        AccionRegatear accion = null;
        Double anguloRegate = null;

        // Dado que vamos a avanzar, solamente nos interesa calcular si el jugador que avanza llega antes al bal�n
        // que los rivales.
        Jugador[] arrayJugadores = new Jugador[] {jugador};
        
        anguloRegate = avanzarRango (sp, jugador, true, arrayJugadores, arrayRivales, 180, -1, -1);
        	
        // Si encontramos un �ngulo para realizar el regate instanciamos la acci�n y salimos del bucle.
        if (anguloRegate != null) {
        	
        	// Recuperamos el �ngulo del regate y lo formateamos por si llegara con valores negativos.
        	double anguloRegateFormateado = Math.toDegrees(anguloRegate);
        	if (anguloRegateFormateado < 0) {
        		anguloRegateFormateado = anguloRegateFormateado + 360;
        	}
        	
        	// Calculamos si es un regate hacia adelante.
        	boolean regateHaciaAdelante = anguloRegateFormateado > 0 && anguloRegateFormateado < 180;
        	
        	// Calculamos si es un regate hacia la porteria, para ello necesitamos los �ngulos m�ximos y m�nimos. 
			double anguloMax = (sp.balon().getX() < 0) ? Math.toDegrees(sp.balon().angulo(Constantes.posteIzqArcoSup)) :
														 180;
			double anguloMin = (sp.balon().getX() < 0) ? 0 :
														 Math.toDegrees(sp.balon().angulo(Constantes.posteDerArcoSup));
			if (anguloMax < 0) {
				anguloMax = anguloMax + 360;
			}
			if (anguloMin < 0) {
				anguloMin = anguloMin + 360;
			}
			
			boolean regateHaciaPorteria = anguloRegateFormateado < anguloMax && anguloRegateFormateado > anguloMin;
        	
        	
        	
        	accion = new AccionRegatear(new ComandoIrA(jugador.getIndice(), 
											sp.misJugadores()[jugador.getIndice()].moverAngulo(anguloRegate, 10)),
										Math.toDegrees(anguloRegate),
										regateHaciaAdelante,
										regateHaciaPorteria
										);
        }

        return accion;
    }
    
    
    
    
    public AccionPasarAlHueco pasarAlHueco (SituacionPartido sp, Jugador jugador, 
    									    Jugador[] arrayJugadores, Rival[] arrayRivales,
    									    Double xRestringida) {
    	
    	AccionPasarAlHueco paseAlHueco = null;
    	
    	double xMax = (xRestringida != null) ? xRestringida : (Constantes.ANCHO_CAMPO_JUEGO / 2) - 1.5;
    	double yMax = yMaxPaseAlHueco(sp, jugador);
    	double yMin = (sp.balon().getY() > 0) ? sp.balon().getY()-20 : sp.balon().getY();
    	
    	
    	Posicion posInicial = new Posicion (0, yMax);
    	Posicion posPase = new Posicion(posInicial);
        	
    	double incremento = (sp.misJugadores()[jugador.getIndice()].getX() > 0) ? 5 : -5;
    	boolean salir = false;
    	boolean buscaOtroLado = true;
    	int jugadorCercanoAlPase;
    	
    	// Buscamos el pase movimiendonos sobre el ejeX.
    	do {
    		
    		jugadorCercanoAlPase = posPase.indiceMasCercano(sp.misJugadores());
    	
    		// Calculamos los par�metros necesarios para llamar al m�todo que calcula si el pase llegar�
			// a su destino.
            double fuerza;
            double anguloZ;
            double anguloXY = Math.toDegrees(sp.misJugadores()[jugador.getIndice()].angulo(posPase));

            // Obtenemos la distancia del pase.
            double distanciaPase = sp.misJugadores()[jugador.getIndice()].distancia(posPase);
            
            // Llegamos con un paso raso
            if (distanciaPase < jugador.getDistanciaGolpeoBajo()) {
                anguloZ = 0;
                fuerza = (Entorno.FUERZA_MAXIMA_GOLPEO * distanciaPase) / jugador.getDistanciaGolpeoBajo();
            }
            else {
                anguloZ = 30;
                fuerza = (Entorno.FUERZA_MAXIMA_GOLPEO * distanciaPase) / jugador.getDistanciaGolpeoAlto();
            }    		
    		
            
            boolean alcanzoBalon = false;
            
            // Comprobamos que no hagamos un pase muy corto para no perder ciclos parados.
            if (distanciaPase > 2) {
    		
		        // Si el pase llega a su destino o a un compa�ero de equipo, lo realizamos.
				alcanzoBalon = alcanzoAntesBalon(sp, jugador, false, 
				  			  					 arrayJugadores, arrayRivales,  
				  			  					 anguloXY, anguloZ, fuerza);
            }
			
			if (alcanzoBalon){
			
				// Recuperamos el �ngulo del pase y lo formateamos por si fuera negativo.
				double anguloPaseFormateado = (anguloXY < 0) ? (anguloXY + 360) : anguloXY; 
				
				// Calculamos si es un pase hacia adelante.
				boolean paseHaciaAdelante = anguloPaseFormateado > 0 && anguloPaseFormateado < 180;
				
				// Calculamos si es un pase hacia el area.
				double xAreaIzquierda = -(Constantes.ANCHO_CAMPO_JUEGO/2 - Constantes.LARGO_AREA_GRANDE/2);
				double xAreaDerecha = (Constantes.ANCHO_CAMPO_JUEGO/2 - Constantes.LARGO_AREA_GRANDE/2);
				double yArea = (Constantes.LARGO_CAMPO_JUEGO /2) - Constantes.ANCHO_AREA_GRANDE;
				double anguloMin, anguloMax;
				if (sp.balon().getX() < xAreaIzquierda) {
					anguloMin = Math.toDegrees(sp.balon().angulo(new Posicion(xAreaDerecha, yArea)));
					anguloMax = Math.toDegrees(sp.balon().angulo(new Posicion(xAreaIzquierda, Constantes.LARGO_CAMPO_JUEGO /2))); 
				}
				else if (sp.balon().getX() < xAreaDerecha) {
					anguloMin = Math.toDegrees(sp.balon().angulo(new Posicion(xAreaDerecha, Constantes.LARGO_CAMPO_JUEGO /2)));
					anguloMax = Math.toDegrees(sp.balon().angulo(new Posicion(xAreaIzquierda, Constantes.LARGO_CAMPO_JUEGO /2))); 
				}
				else {
					anguloMin = Math.toDegrees(sp.balon().angulo(new Posicion(xAreaDerecha, Constantes.LARGO_CAMPO_JUEGO /2)));
					anguloMax = Math.toDegrees(sp.balon().angulo(new Posicion(xAreaIzquierda, yArea)));
				}
				if (anguloMin < 0) {
					anguloMin = anguloMin + 360;
				}
				if (anguloMax < 0) {
					anguloMax = anguloMax + 360;
				}
				
				boolean paseHaciaElArea = anguloPaseFormateado < anguloMax && anguloPaseFormateado > anguloMin;
				
    			paseAlHueco = new AccionPasarAlHueco(
    								new ComandoGolpearBalon(jugador.getIndice(),
    														anguloXY,
                        									fuerza,
                        									anguloZ),
                        			jugadorCercanoAlPase,
                        			anguloXY,
                        			paseHaciaAdelante,
                        			paseHaciaElArea);
    		}
    		else {
    			
    			if (Math.abs(posPase.getX()) < xMax) {
    				posPase = posPase.moverPosicion(incremento, 0);
    			}
    			else {
    				
    				if (buscaOtroLado) {
    					
    					buscaOtroLado = false;
    					incremento = -incremento;
    					posPase = new Posicion (posInicial);
    				}
    				else {
    					
    					// Si tenemos margen de b�squeda en el eje Y reducimos la y un incremento e 
    					// inicializamos todas las variables para buscar en esa posici�n.
    					if (yMax > yMin) {
    						
    						buscaOtroLado = true;
    						incremento = (sp.misJugadores()[jugador.getIndice()].getX() > 0) ? 5 : -5;
    						
    						yMax = yMax - 5;
    						posInicial = new Posicion(posInicial.getX(), yMax);
    						posPase = new Posicion (posInicial);
    					}
    					else {
    						salir = true;
    					}
    				}
    			}
            }
    	} while (paseAlHueco == null && !salir);
    	
    	return paseAlHueco;
    }
    
    
    
    public AccionPasarAlArea pasarAlArea (SituacionPartido sp, Jugador jugador, 
    									  Jugador[] arrayJugadores, Rival[] arrayRivales) {
    	
    	AccionPasarAlArea paseAlArea = null;
    	Jugador jugadorDestino = arrayJugadores[Constantes.penalSup.indiceMasCercano(sp.misJugadores())];
    	
    	// Variable en la que devolveremos el resultado.
    	boolean realizaPase = false;

		// Calculamos los par�metros necesarios para llamar al m�todo que calcula si el pase llegar�
		// a su destino.
        double fuerza;
        double anguloZ;
        double anguloXY = Math.toDegrees(sp.balon().angulo(new Posicion(Constantes.penalSup.getX(),
        																Constantes.penalSup.getY() + 3)));

        // Obtenemos la distancia del pase.
        double distanciaPase = sp.misJugadores()[jugador.getIndice()].distancia(Constantes.penalSup);

        anguloZ = 35;
        fuerza = ((Entorno.FUERZA_MAXIMA_GOLPEO * distanciaPase) / jugador.getDistanciaGolpeoAlto()) + 0.2;
        fuerza = Math.max(fuerza, 0.7);
    	
    	double amplitudAngular = 15;
        
    	
    	double incremento = (sp.misJugadores()[jugador.getIndice()].getX() < 0) ? 5 : -5;
    	double angulo = anguloXY;
    	
    	boolean salir = false;
    	boolean otroLado = false;
    	long contador = 0;
    	
        do {
        	realizaPase = alcanzoAlturaDespeje(sp, jugador, arrayRivales, angulo, anguloZ, fuerza);

    		// Si no podemos realizar el despeje para los datos obtenidos.
    		if (!realizaPase) {
    			
    			// Si no hemos superado la amplitud angular, realizamos un nuevo incremento. 
    			if ((Math.abs(incremento) * contador)  < amplitudAngular) {
    				
    				// Si tenemos que buscar el �ngulo equivalente al otro lado.
    				if (otroLado) {
    					angulo = anguloXY - (incremento * contador);
    				}
    				// Si no tenemos que buscar el �ngulo al otro lado.
    				else {
    					
    		    		// Aumemtamos el contador de la iteraci�n.
    		    		contador ++;
    		    		
    					angulo = (incremento * contador) + anguloXY;
    				}
    			}
    			else {
    				salir = true;
    			}
    		}
    		
    		// Cambiamos el valor de la variable que controla si hemos buscado el �ngulo del otro lado.
    		otroLado = !otroLado;
    		
    	} while (!realizaPase && !salir);
    	
    	
    	
    	// Si hemos encontrado un �ngulo de despeje, realizamos el despeje.
    	if (realizaPase) {
    		paseAlArea = new AccionPasarAlArea(new ComandoGolpearBalon(jugador.getIndice(),
                    					  				  		  		angulo,
                    					  				  		  		fuerza,
                    					  				  		  		anguloZ),
                    					  	   jugadorDestino);
    	}
    	// En caso contrario despejamos hacia el centro de la porter�a.
    	else {
    		paseAlArea = new AccionPasarAlArea(new ComandoGolpearBalon(jugador.getIndice(),
    																   Constantes.penalSup,
    																   1,
    																   50),
    								           jugadorDestino);
    	}
    	
    	return paseAlArea;
    }
    
    
    // M�todo que calcula si un rival puede interceptar un pase.
    public boolean rivalEnTrayectoria(SituacionPartido sp,
    		 						  Posicion origen,
                                      Posicion destino,
                                      Double distanciaCorte) {

        boolean enTrayectoria = false;

        // Calculamos la f�rmula de la recta de la trayectoria seg�n la ecuaci�n
        // (y-y1)(x2-x1) = (y2-y1)(x-x1).
        double A = -(destino.getY() - origen.getY());
        double B = destino.getX() - origen.getX();
        double C = origen.getX() * destino.getY() - origen.getY() * destino.getX();

        // Variables que nos permitiran establecer la zona de riesgo entre el origen y el destino.
        double xIzq = (origen.getX() < destino.getX()) ? origen.getX() : destino.getX();
        double xDer = (origen.getX() > destino.getX()) ? origen.getX() : destino.getX();
        double ySup = (origen.getY() > destino.getY()) ? origen.getY() : destino.getY();
        double yInf = (origen.getY() < destino.getY()) ? origen.getY() : destino.getY();
        
        
        // Ampliamos el rango dado cuando nos encontramos una situaci�n en la que dejamos rivales que pueden
        // interceptar potencialmente el pase.
        if (xDer - xIzq < 2) {
        	xIzq = xIzq - 2;
        	xDer = xDer + 2;
        }
        if (ySup - yInf < 2) {
        	ySup = ySup + 2;
        	yInf = yInf - 2;
        }


        // Comprobamos si los rivales se encuentran en la trayectoria del pase.
        for (Posicion rival:sp.rivales()) {

            // Comprobamos si el rival se encuentra en la zona del pase.
            if (rival.getX() >= xIzq && rival.getX() <= xDer &&
                rival.getY() <= ySup && rival.getY() >= yInf) {
                // Obtenemos la distancia del rival a la recta de la trayectoria seg�n la ecuaci�n
                // |xP*A + yP*B + C| / (A^2 + B^2)^sqr.
                double distancia = Math.abs((rival.getX() * A) + (rival.getY() * B) + C) /
                                   Math.sqrt((A*A) + (B*B));
                if (distancia < distanciaCorte) {
                    enTrayectoria = true;
                    break;
                }
            }
        }

        return enTrayectoria;
    }
    
    
    // M�todo que devuelve true si el tiro es a puerta vacia.
    public boolean puertaVacia(SituacionPartido sp, Posicion posTiro) {
        
    	boolean vacia = true;
    	
    	// Si nos encontramos en una Y muy alta anulamos el tiro a puerta vacia
    	if (posTiro.getX() > Math.abs(Entorno.X_ANULA_PUERTA_VACIA) &&
    		posTiro.getY() > Entorno.Y_ANULA_PUERTA_VACIA) {
    		
    		vacia = false;
    	}
    	else {
    	
	    	// Recorremos todos los rivales.
	    	for (Posicion r:sp.rivales()) {
	    		
	    		// Comprobamos que el rival se encuentre m�s adelantado.
	    		if (r.getY() > posTiro.getY() && r.getY() < Constantes.centroArcoSup.getY()) {
	    			
	    			// Si el rival se encuentra a la izquierda del bal�n comprobamos la intersecci�n de su l�nea
	    			// horizontal con la recta formada entre el balon y el poste izquierdo.
	    			if (r.getX() < posTiro.getX()) {
	    				
	    				Posicion interseccion = Posicion.Interseccion(posTiro, Constantes.posteIzqArcoSup,
	    															  new Posicion(r.getX() - 50, r.getY()), 
	    															  new Posicion(r.getX() + 50, r.getY()));
	    				
	    				if (interseccion != null && interseccion.getX() < r.getX()) {
	    					vacia = false;
	    					break;
	    				}
	    			}
	    			// Si el rival se encuentra a la derecha del bal�n comprobamos la intersecci�n de su l�nea
	    			// horizontal con la recta formada entre el balon y el poste derecho.
	    			else {
	    				
	    				Posicion interseccion = Posicion.Interseccion(posTiro, Constantes.posteDerArcoSup,
	    															  new Posicion(r.getX() - 50, r.getY()), 
	    															  new Posicion(r.getX() + 50, r.getY()));
	
	    				if (interseccion != null && interseccion.getX() > r.getX()) {
	    					vacia = false;
	    					break;
	    				}    				
	    			}
	    		}
	    	}
    	}
    	
    	return vacia;
    }    
 
    public double redondeaMultiplo(double valor, double divisor) {
        return Math.round(valor / divisor) * divisor;
    }
    
    public boolean posicionDentroDelCampo (Posicion posicion) {
    	
    	double mx = Constantes.ANCHO_CAMPO_JUEGO / 2;
    	double my = Constantes.LARGO_CAMPO_JUEGO / 2;
    	
    	// Si la posici�n se encuentra dentro del campo de juego.
    	return posicion.getX() >= -mx && posicion.getX() <= mx &&
    		   posicion.getY() >= -my && posicion.getY() <= my;

    }
    
    
    
    
    //*************************************************************************************************************
    //										M�TODOS PRIVADOS DE LA CLASE
    //*************************************************************************************************************
    
    
    
    
    private boolean cumpleCondicionesPase(SituacionPartido sp, Jugador jugador, Jugador jugadorDestino) {
    	
    	boolean cumpleCondiciones = false;

    	// Comprobamos si el jugador que realiza el pase tiene la suficiente fuerza como para llegar al destino.
    	if (sp.misJugadores()[jugador.getIndice()].distancia(sp.misJugadores()[jugadorDestino.getIndice()]) <=
    		jugador.getDistanciaGolpeoAlto()) {
    		
	    	// Si el jugador que realiza el pase es el portero comprobamos que si el jugador destino del 
			// pase es un defensa no tenga ning�n rival cerca.
			if (sp.detalleMisJugadores()[jugador.getIndice()].esPortero()) {
				
				int rivalCercano = sp.misJugadores()[jugadorDestino.getIndice()].indiceMasCercano(sp.rivales());
				if (jugadorDestino.getIndice() < 5 &&
					sp.misJugadores()[jugadorDestino.getIndice()].distancia(sp.rivales()[rivalCercano]) > 
				    Entorno.DISTANCIA_RIVALES_PASE_PORTERO) {
					
					cumpleCondiciones = true;
				}
			}
	    	
	    	// Comprobamos si el pase es hacia adelante y el destino tiene menos jugadores por delante y la
	    	// distancia es mayor que la m�nima exigida para realizar un pase hacia adelante.
			else if ((sp.misJugadores()[jugadorDestino.getIndice()].getY() > 
	    	     	 sp.misJugadores()[jugador.getIndice()].getY()) &&
	    	    
	    	     	 (rivalesPorEncimaY(sp, jugadorDestino) < rivalesPorEncimaY(sp, jugador)) &&
	    	    
	    	     	 (sp.misJugadores()[jugador.getIndice()].distancia(
	    				sp.misJugadores()[jugadorDestino.getIndice()]) > Entorno.MIN_DISTANCIA_PASE_ADELANTE)
	    	    
	    			) {
	    		
	    		cumpleCondiciones = true;
	    	}
	    	
	    	// En caso contrario comprobamos que la distancia m�nima sea superior a la establecida.
	    	else if (sp.misJugadores()[jugador.getIndice()].distancia(
					 sp.misJugadores()[jugadorDestino.getIndice()]) > Entorno.MIN_DISTANCIA_PASE) {
	    		
	    		cumpleCondiciones = true;
	    	}
    	}
    	
    	return cumpleCondiciones;
    }

    
    private boolean destinoDentroArea (SituacionPartido sp, Jugador destino) {
    	
    	boolean dentroArea = false;
    	
    	if (sp.misJugadores()[destino.getIndice()].getY() > 
    			((Constantes.LARGO_CAMPO_JUEGO /2) - Constantes.ANCHO_AREA_GRANDE) &&
    		Math.abs(sp.misJugadores()[destino.getIndice()].getX()) < Constantes.LARGO_AREA_GRANDE/2) {
    		
    		dentroArea = true;
    	}
    	
    	return dentroArea;
    }
    
    private long rivalesPorEncimaY (SituacionPartido sp, Jugador jugador) {
    	
    	long rivales = 0;

    	double margen = 10;
    	for (Posicion posRival:sp.rivales()) {
    		
    		if (posRival.getY() > sp.misJugadores()[jugador.getIndice()].getY() &&
    			posRival.getX() > sp.misJugadores()[jugador.getIndice()].getX() - margen &&
    			posRival.getX() < sp.misJugadores()[jugador.getIndice()].getX() + margen) {
    			
    			rivales ++;
    		}
    	}
    	
    	return rivales;
    }
    
    
    private double yMaxPaseAlHueco (SituacionPartido sp, Jugador jugador) {
    	
    	double yMax = (Constantes.LARGO_CAMPO_JUEGO / 2) - 1.5;
    	double y = Constantes.centroArcoInf.getY();
    	
    	// Nos quedamos con la Y de nuestro jugador m�s adelantado. Buscamos de la media en adelante.
    	for (int i=5; i<sp.misJugadores().length; i++) {
    		
    		if (sp.misJugadores()[i].getY() > y) {
    			y = sp.misJugadores()[i].getY();
    		}
    	}
    	
    	// Hacemos lo propio buscando el jugador m�s retrasado de los rivales.
    	for (int i=0; i<sp.rivales().length; i++) {
    		
    		// Omitimos al portero ya que este probablemente se encontrara cerca de la l�nea de fondo.
    		if (!sp.detalleJugadoresRivales()[i].esPortero() && sp.rivales()[i].getY() > y) {
    			y = sp.rivales()[i].getY();
    		}
    	}
    	
    	// Aumentamos en 20 la posici�n Y del jugador m�s adelantado para buscar la profundidad, pero 
    	// controlando que lleguemos a esa distancia siempre realizando una cuenta en perperndicular.
    	y = y+20;
    	if (y > jugador.getDistanciaGolpeoAlto()) {
    		y = jugador.getDistanciaGolpeoAlto();
    	}
    	
    	// Devolvemos el valor m�s peque�o entre la y calculada y la y m�xima, ya que no nos podemos salir 
    	// del campo. 
    	return Math.min(y, yMax);
    }
    
    private boolean alcanzoAntesBalon(SituacionPartido sp, Jugador jugador, boolean avance, 
    								  Jugador[] arrayJugadores, Rival[] arrayRivales,  
    							 	  double anguloXY, double anguloZ, double fuerza) {
    	
    	boolean alcanzoAntesBalon = false;
    	double balonDx = 0, balonDy = 0, balonDz = 0;
    	
    	// Definimos los diferencias para el caso del avance.
    	if (avance) {

    		//NOTA: En este caso el anguloXY debe llegar informado en radianes.
    		
    		double vel = Constantes.getVelocidad(sp.detalleMisJugadores()[jugador.getIndice()].getVelocidad())+.2;
	    	
	    	balonDz = 0;
	    	balonDx = Math.cos(anguloXY) * vel;
	    	balonDy = Math.sin(anguloXY) * vel;
    	}
    	// Definimos los diferenciales para el caso del golpeo del bal�n.
    	else {

    		//NOTA: En este caso el anguloXY debe llegar informado en grados.
    		
    		 double vel = fuerza * Constantes.getVelocidadRemate(sp.detalleMisJugadores()[jugador.getIndice()].getRemate());
             
             balonDz = 0;
             balonDx = 0;
             balonDy = 0;
             
             if (vel != 0) {

            	 
                 double angVer = Math.min(anguloZ, Constantes.ANGULO_VERTICAL_MAX);
                 angVer = Math.max(angVer, 0);
                 angVer = angVer * (Math.PI / 180d);

                 anguloXY = anguloXY * (Math.PI / 180d);
                 
	             vel = vel * Math.cos(angVer);
	             balonDz = redondeaMultiplo(vel * Math.sin(angVer), Constantes.G);
	             balonDx = Math.cos(anguloXY) * vel;
	             balonDy = Math.sin(anguloXY) * vel;
             }
    	}
         
         // Establecemos la altura inicial en cero.
         double alturaBalon = 0;
         
         boolean salir = false;
         long ciclos = 0;
         Posicion balon = new Posicion(sp.balon());
         
         
         // Una vez definidas las variables del golpeo, vamos a iterar hasta comprobar si nuestro jugador llega 
         // antes a por el bal�n o en cambio llega antes un rival.
         while (!alcanzoAntesBalon && !salir &&
				posicionDentroDelCampo(balon) &&
			   (Math.abs(balonDx) > 0.01 || Math.abs(balonDy)> 0.01)) {
				
			// Incrementamos el ciclo del c�lculo en el que nos encontramos.
			ciclos ++;
			
			// Actualizamos la posici�n del bal�n correspondiendose al avance del ciclo.
			if (alturaBalon == 0 && balonDz >= 0 && balonDz < Constantes.G * 3) {

				// Actualizamos la posici�n del bal�n.
				balon = balon.moverPosicion(balonDx, balonDy);
				
				// Incrementamos los diferenciales.
				balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
				balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
			}
			else {
				
				// Actualizamos la posici�n del bal�n.
				balon = balon.moverPosicion(balonDx, balonDy);
				alturaBalon = redondeaMultiplo(alturaBalon + balonDz, Constantes.G);
				
				// Incrementamos los diferenciales.
				balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
	            balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
	    		balonDz = redondeaMultiplo(balonDz - Constantes.G, Constantes.G);
	    		
	    		if (alturaBalon == 0) {
	                balonDz = (-balonDz - Constantes.G) * Constantes.FACTOR_DISMINUCION_ALTURA_BALON_REBOTE;
	                balonDz = redondeaMultiplo(balonDz, Constantes.G);
	            }
			}
			
			
			// Comprobamos si en el ciclo actual mis jugadores alcanzan el bal�n.
			boolean jugadorAlcanzaBalon = false;
			for (Jugador miJugador:arrayJugadores) {

				// Tenemos que comprobar si han pasado las iteraciones que nos permiten volver a tocar el 
				// bal�n por lo que testeamos que si el jugador a comprobar es el mismo que el que realiza el 
				// golpeo hayan pasado m�s de 10 iteraciones y en caso de que el jugador sea distinto.
				if ((miJugador.getIndice() == jugador.getIndice() && ciclos > Constantes.ITERACIONES_GOLPEAR_BALON) ||
					(miJugador.getIndice() != jugador.getIndice() && 
					 (sp.iteracionesParaRematar()[miJugador.getIndice()]-ciclos <= 0))) {
				
					jugadorAlcanzaBalon =
						futbolistaAlcanzaElBalon (sp.misJugadores()[miJugador.getIndice()], 
												  sp.detalleMisJugadores()[miJugador.getIndice()].getVelocidad(), 
												  balon, alturaBalon, ciclos,
												  Constantes.DISTANCIA_CONTROL_BALON, 
												  (sp.detalleMisJugadores()[miJugador.getIndice()].esPortero() ? 
														  Constantes.ALTO_ARCO : Constantes.ALTURA_CONTROL_BALON));
					
					if (jugadorAlcanzaBalon) {
						break;
					}
				}
			}
			
			// Comprobamos si en el ciclo actual alg�n rival alcanza el bal�n.
			boolean rivalAlcanzaBalon = false;
			for (Rival rival:arrayRivales) {
				
				// Si en el ciclo calculado el jugador puede rematar el bal�n, calculamos si alcanza el bal�n.
				if ((sp.iteracionesParaRematarRival()[rival.getIndice()]-ciclos) <= 0) {
					
					rivalAlcanzaBalon = 
						futbolistaAlcanzaElBalon (sp.rivales()[rival.getIndice()], 
												  sp.detalleJugadoresRivales()[rival.getIndice()].getVelocidad(), 
												  balon, alturaBalon, ciclos,
												  Constantes.DISTANCIA_CONTROL_BALON, 
												  (sp.detalleJugadoresRivales()[rival.getIndice()].esPortero() ? 
														  Constantes.ALTO_ARCO : Constantes.ALTURA_CONTROL_BALON));
					
					if (rivalAlcanzaBalon) {
						break;
					}
				}
			}
			
			
			// Si un jugador mio alcanza antes el bal�n devolvemos true.
			if (jugadorAlcanzaBalon && !rivalAlcanzaBalon) {
				alcanzoAntesBalon = true;
			}
			// En cambio si un jugador rival alcanza antes el bal�n, nos salimos devolviendo false.
			else if (rivalAlcanzaBalon) {
				salir = true;
			}
		}
    	
    	return alcanzoAntesBalon;
    }
    
    

    private boolean alcanzoAlturaDespeje(SituacionPartido sp, Jugador jugador, Rival[] arrayRivales,  
    									 double anguloXY, double anguloZ, double fuerza) {
    	
    	// Variable en la que devolvemos el resultado.
    	boolean alcanzoAlturaDespeje = false;

    	// Definimos los diferenciales para el caso del golpeo del bal�n.
   		//NOTA: En este caso el anguloXY debe llegar informado en grados.
    	double balonDx = 0, balonDy = 0, balonDz = 0;
    	double vel = fuerza * Constantes.getVelocidadRemate(sp.detalleMisJugadores()[jugador.getIndice()].getRemate());

    	balonDz = 0;
    	balonDx = 0;
    	balonDy = 0;

    	if (vel != 0) {


    		double angVer = Math.min(anguloZ, Constantes.ANGULO_VERTICAL_MAX);
    		angVer = Math.max(angVer, 0);
    		angVer = angVer * (Math.PI / 180d);

    		anguloXY = anguloXY * (Math.PI / 180d);

    		vel = vel * Math.cos(angVer);
    		balonDz = redondeaMultiplo(vel * Math.sin(angVer), Constantes.G);
    		balonDx = Math.cos(anguloXY) * vel;
    		balonDy = Math.sin(anguloXY) * vel;
    	}

    	// Establecemos la altura inicial en cero.
    	double alturaBalon = 0;

    	// Una vez definidas las variables del golpeo, vamos a iterar hasta comprobar si el bal�n alcanza
    	// una altura a la que no llega ning�n jugador sin interceptarlo previamente.
    	boolean rivalAlcanzaBalon = false;
    	boolean salir = false;
    	long ciclos = 0;
    	Posicion balon = new Posicion(sp.balon());
    	
    	while (!rivalAlcanzaBalon && !salir) {

    		// Incrementamos el ciclo del c�lculo en el que nos encontramos.
    		ciclos ++;

    		// Actualizamos la posici�n del bal�n correspondiendose al avance del ciclo.
    		if (alturaBalon == 0 && balonDz >= 0 && balonDz < Constantes.G * 3) {

    			// Actualizamos la posici�n del bal�n.
    			balon = balon.moverPosicion(balonDx, balonDy);

    			// Incrementamos los diferenciales.
    			balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
    			balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
    		}
    		else {

    			// Actualizamos la posici�n del bal�n.
    			balon = balon.moverPosicion(balonDx, balonDy);
    			alturaBalon = redondeaMultiplo(alturaBalon + balonDz, Constantes.G);

    			// Incrementamos los diferenciales.
    			balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
    			balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
    			balonDz = redondeaMultiplo(balonDz - Constantes.G, Constantes.G);

    			if (alturaBalon == 0) {
    				balonDz = (-balonDz - Constantes.G) * Constantes.FACTOR_DISMINUCION_ALTURA_BALON_REBOTE;
    				balonDz = redondeaMultiplo(balonDz, Constantes.G);
    			}
    		}

 		
    		// Comprobamos si en el ciclo actual alg�n rival alcanza el bal�n.
    		for (Rival rival:arrayRivales) {

    			// Si en el ciclo calculado el jugador puede rematar el bal�n, calculamos si alcanza el bal�n.
    			if ((sp.iteracionesParaRematarRival()[rival.getIndice()]-ciclos) <= 0) {

    				rivalAlcanzaBalon = 
    					futbolistaAlcanzaElBalon (sp.rivales()[rival.getIndice()], 
    							sp.detalleJugadoresRivales()[rival.getIndice()].getVelocidad(), 
    							balon, alturaBalon, ciclos,
    							Constantes.DISTANCIA_CONTROL_BALON, 
    							(sp.detalleJugadoresRivales()[rival.getIndice()].esPortero() ? 
    									Constantes.ALTO_ARCO : Constantes.ALTURA_CONTROL_BALON));

    				if (rivalAlcanzaBalon) {
    					break;
    				}
    			}
    		}


    		if (!rivalAlcanzaBalon && alturaBalon > Constantes.ALTURA_CONTROL_BALON) {
    			salir = true;
    			alcanzoAlturaDespeje = true;
    		}
    	}

    	return alcanzoAlturaDespeje;
    }    
    
    
    
    
    // Nota: Recordamos que en el avance los angulos se especifican en radianes y el golpeo en grados.  
    private Double avanzarRango (SituacionPartido sp, Jugador jugador, boolean avance,
    							 Jugador[] arrayJugadores, Rival[] arrayRivales, 
    							 double amplitudAngular, double anguloVertical, double fuerza) {
    	
    	Double anguloRango = null;
    	double incremento = (avance ? Math.abs(Math.toRadians(90) - Math.toRadians(95)): 5);
    	double anguloInicial;
    	
    	if (avance) {
    		
    		// Si hemos superado el l�mite de avance en Y, recuperamos el �ngulo de avance hacia la porter�a.
    		if (sp.misJugadores()[jugador.getIndice()].getY() > Entorno.LIMITE_AVANCE_Y) {
    			anguloInicial = sp.misJugadores()[jugador.getIndice()].angulo(Constantes.centroArcoSup);
    		}
    		else {
    			anguloInicial = Math.toRadians(90);	
    		}
    	}
    	else {
    		
    		// Si hemos superado el l�mite de avance en Y, recuperamos el �ngulo de avance hacia la porter�a.
    		if (sp.misJugadores()[jugador.getIndice()].getY() > Entorno.LIMITE_AVANCE_Y) {
    			anguloInicial = Math.toDegrees(sp.misJugadores()[jugador.getIndice()].angulo(Constantes.centroArcoSup));
    		}
    		else {
    			anguloInicial = 90;
    		}
    	}
    	
    	// Si estamos en el lado izquierdo buscamos primero avanzar hacia la porter�a (incremento angular negativo)
    	if (sp.misJugadores()[jugador.getIndice()].getX() < 0) {
    		incremento = -incremento;
    	}
    	
    	if (avance) {
    		amplitudAngular = Math.toRadians(amplitudAngular);
    	}

    	double angulo = anguloInicial;
    	
    	
    	boolean salir = false;
    	boolean alcanzoBalon = false;
    	boolean otroLado = false;
    	long contador = 0;
    	
    	do {
    		
    		alcanzoBalon = alcanzoAntesBalon(sp, jugador, avance, 
					 						 arrayJugadores, arrayRivales,  
					 						 angulo, anguloVertical, fuerza);
    		

    		// Si alcanzamos el bal�n nos quedamos con el �ngulo para devolverlo.
    		if (alcanzoBalon) {
    			anguloRango = angulo;
    		}
    		// Si no alcanzamos el bal�n comprobamos si podemos seguir variando el �ngulo del desplazamiento
    		// para seguri buscando.
    		else {
    			
    			// Si no hemos superado la amplitud angular, realizamos un nuevo incremento. 
    			if ((Math.abs(incremento) * contador)  < amplitudAngular) {
    				
    				// Si tenemos que buscar el �ngulo equivalente al otro lado.
    				if (otroLado) {
    					angulo = anguloInicial - (incremento * contador);
    				}
    				// Si no tenemos que buscar el �ngulo al otro lado.
    				else {
    					
    		    		// Aumemtamos el contador de la iteraci�n.
    		    		contador ++;
    		    		
    					angulo = (incremento * contador) + anguloInicial;
    				}
    			}
    			else {
    				salir = true;
    			}
    		}
    		
    		// Cambiamos el valor de la variable que controla si hemos buscado el �ngulo del otro lado.
    		otroLado = !otroLado;
    		
    	} while (!alcanzoBalon && !salir);
    
    	return anguloRango;
    }
    
    
    
    // M�todo que nos permite conocer si un futbolista (jugador o rival) alcanza el bal�n en la situaci�n
    // dada por los par�metros establecidos.
    private boolean futbolistaAlcanzaElBalon (Posicion futbolista, double velocidadFutbolista, 
    										  Posicion balon, double alturaBalon, long iteraciones,
    										  double alcanceXY, double alcanceZ) {
    	
    	boolean alcanzaElBalon = false;
    	
		// Si el jugador alcanza el bal�n en el eje Z.
		if (alturaBalon <= alcanceZ) {
		
			Posicion irA = new Posicion(balon.getX(), balon.getY());
			
			double dist = futbolista.distancia(irA);
			double vel = Constantes.getVelocidad(velocidadFutbolista) * iteraciones;
	        if (dist < vel) {
	            vel = dist;
	        }
	        futbolista = futbolista.moverPosicion(irA.getX() - futbolista.getX(), 
	        									  irA.getY() - futbolista.getY(), vel);
	        
	        
	        if (futbolista.distancia(irA) < alcanceXY) {
	        	alcanzaElBalon = true;
	        }
		}
		
		return alcanzaElBalon;
    }
    
    
    private Hashtable<String, Object> calculaPosicionTiro(SituacionPartido sp, Jugador jugador, 
    													  Posicion inicioTiro, boolean opcionTiroSeguro) {
    	
    	// Variable en la que devolveremos el resultado.
    	Hashtable<String, Object> tabla = null;
    	double varIni = 0.0, varFin = 0.0;
    	
    	ArrayList<String> listadoZona = listaZonaTiro(sp, jugador, inicioTiro, opcionTiroSeguro);

    	// Tenemos informaci�n disponible en el listado con las zonas encontradas para realizar un tiro. 
    	if (listadoZona != null && listadoZona.size() > 0) {

    		double ini = 0.0, fin = 0.0;
    		for (String zona:listadoZona) {

    			String[] arr = zona.split("#");
    			ini = new Double(arr[0]);
    			fin = new Double(arr[1]);

    			// Comprobamos si nos encntramos en el caso inicial o la zona actual es m�s amplia que la 
    			// anterior para quedarnos con la m�s amplia.
    			if ( (varIni == 0.0 && varFin == 0.0) || (fin - ini) > (varFin - varIni) ) {
    				varIni = ini;
    				varFin = fin;
    			}
    		}

    		// Calculamos la variaci�n a aplicar al poste izquierdo para calcular el punto medio de la
    		// variaci�n obtenida
    		double variacion;
    		if (varFin == varIni) {
    			variacion = varFin;
    		} else {
    			
    			variacion = (varFin + varIni) / 2;
    		}

    		tabla = new Hashtable<String, Object>();
    		tabla.put("posicion", Constantes.posteIzqArcoSup.moverPosicion(variacion, 0));
    		tabla.put("variacion", (varFin - varIni));
    	}
    	
    	return tabla;
    }    
 

    private ArrayList<String> listaZonaTiro (SituacionPartido sp, Jugador jugador, 
    										 Posicion inicioTiro, boolean opcionTiroSeguro) {
    	
    	ArrayList<String> lista = new ArrayList<String>();
    	
    	double incremento = 0.2;
    	double variacion = 0.0;

    	// Comenzamos a recorrer desde el poste izquierdo de la porteria hasta el poste derecho buscando los
    	// huecos libres para tirar.
    	boolean zonaTiro = false;
    	Double inicioZona = null;
    	Posicion posTiro = new Posicion(Constantes.posteIzqArcoSup);
    	
    	// Variable en la que almacenaremos el resultado de realizar el c�lculo del tiro atendiendo a si es
    	// un tiro seguro o no.
    	boolean llegaAGol;
    	
    	do {
    		posTiro = posTiro.moverPosicion(incremento, 0);
    		variacion = variacion + incremento;
    		
    		// Calcular la trayectoria con la opci�n de tiro seguro.
    		if (opcionTiroSeguro) {
    			llegaAGol = trayectoriaDeGol(sp, jugador, inicioTiro, posTiro);
    		}
    		// Calcular la trayectoria sin la opci�n de tiro seguro (es decir, si tenemos v�a libre).
    		else {
    			llegaAGol = !rivalEnTrayectoria(sp, inicioTiro, posTiro, Entorno.DISTANCIA_CORTE_TIRO) &&
    						!porteroEnTrayectoria(sp, inicioTiro, posTiro);
    		}
    		
    		// Si no hay rival en la trayectoria del tiro.
    		if (llegaAGol) {
    		
    			// Si antes no en zona de tiro, marcamos esta posicion como la de inicio de la 
    			// zona de tiro libre.
    			if (!zonaTiro) {
    				zonaTiro = true;
    				inicioZona = variacion;
    			}
    		}
    		// Hay rival en la trayectoria del equipo.
    		else {
    			
    			// Si nos encontramos en zona de tiro cerramos la zona con la variaci�n actual e indicamos que
    			// nos encontramos fuera de ella nuevamente. 
    			if (zonaTiro) {
    				zonaTiro = false;
    				lista.add(inicioZona + "#" + (variacion - incremento));
    				inicioZona = null;
    			}
    		}
    	} while (posTiro.getX() < Constantes.posteDerArcoSup.getX());
    	
    	
    	// Si finalizamos el bucle y tenemos una zona de tiro abierta la cerramos. 
    	if (zonaTiro) {
    		
    		if ((variacion-incremento > inicioZona)) {
    			lista.add(inicioZona + "#" + (variacion - incremento));
    		}
    		else {
    			lista.add(inicioZona + "#" + inicioZona);
    		}
    	}
    	
    	return lista;
    }    
 
    
    // M�todo que devuelve true si nos encontramos en la zona de forzado de tiro.
    private boolean forzadoTiro(SituacionPartido sp) {
    	
    	boolean forzado = sp.balon().distancia(Constantes.centroArcoSup) < Entorno.DISTANCIA_FORZAR_TIRO;
    	
    	// Si nos encontramos en la distancia de forzado y el bal�n se encuentra fuera del eje de las x de la 
    	// posici�n que delimita la porteria, comprobamos que el �ngulo de tiro se encuentre dentro del rango. 
    	if (forzado && Math.abs(sp.balon().getX()) > Constantes.posteDerArcoSup.getX() + 1.5) {
    		
    		double incremento = 30;
    		double anguloTiro = Math.toDegrees(sp.balon().angulo(Constantes.centroArcoSup));
    		forzado = (anguloTiro > (0 + incremento)) && (anguloTiro < (180 - incremento));
    	}
    	
    	
    	return forzado;
    }
    
    
    private boolean trayectoriaDeGol (SituacionPartido sp, Jugador jugador, 
    								  Posicion inicioTiro, Posicion destinoTiro) {
    	
   		//NOTA: En este caso el anguloXY debe llegar informado en grados.
    	double anguloXY = Math.toDegrees(inicioTiro.angulo(destinoTiro));
    	double vel = Entorno.FUERZA_MAXIMA_GOLPEO * Constantes.getVelocidadRemate(sp.detalleMisJugadores()[jugador.getIndice()].getRemate());
        
    	// Definimos los diferenciales.
    	double balonDz = 0;
    	double balonDx = 0;
    	double balonDy = 0;

    	if (vel != 0) {

    		double angVer = Math.min(jugador.getAnguloTiro(), Constantes.ANGULO_VERTICAL_MAX);
    		angVer = Math.max(angVer, 0);
    		angVer = angVer * (Math.PI / 180d);

    		anguloXY = anguloXY * (Math.PI / 180d);

    		vel = vel * Math.cos(angVer);
    		balonDz = redondeaMultiplo(vel * Math.sin(angVer), Constantes.G);
    		balonDx = Math.cos(anguloXY) * vel;
    		balonDy = Math.sin(anguloXY) * vel;
    	}

    	// Establecemos la altura inicial en cero.
    	double alturaBalon = 0;

    	boolean salir = false;
    	long ciclos = 0;
    	Posicion balon = new Posicion(inicioTiro);
         
    	boolean rivalAlcanzaBalon = false;
    	
    	// Una vez definidas las variables del golpeo, vamos a iterar hasta comprobar si algun rival 
    	// alcanza el bal�n.
         while (!rivalAlcanzaBalon && !salir &&
				posicionDentroDelCampo(balon) &&
			   (Math.abs(balonDx) > 0.01 || Math.abs(balonDy)> 0.01)) {
				
			// Incrementamos el ciclo del c�lculo en el que nos encontramos.
			ciclos ++;
			
			// Actualizamos la posici�n del bal�n correspondiendose al avance del ciclo.
			if (alturaBalon == 0 && balonDz >= 0 && balonDz < Constantes.G * 3) {

				// Actualizamos la posici�n del bal�n.
				balon = balon.moverPosicion(balonDx, balonDy);
				
				// Incrementamos los diferenciales.
				balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
				balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
			}
			else {
				
				// Actualizamos la posici�n del bal�n.
				balon = balon.moverPosicion(balonDx, balonDy);
				alturaBalon = redondeaMultiplo(alturaBalon + balonDz, Constantes.G);
				
				// Incrementamos los diferenciales.
				balonDx = balonDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
	            balonDy = balonDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
	    		balonDz = redondeaMultiplo(balonDz - Constantes.G, Constantes.G);
	    		
	    		if (alturaBalon == 0) {
	                balonDz = (-balonDz - Constantes.G) * Constantes.FACTOR_DISMINUCION_ALTURA_BALON_REBOTE;
	                balonDz = redondeaMultiplo(balonDz, Constantes.G);
	            }
			}

			// Comprobamos si en el ciclo actual alg�n rival alcanza el bal�n.
			Posicion[] arrayRivales = sp.rivales();
			//for (Rival rival:arrayRivales) {
			int contador = 0;
			while (contador < arrayRivales.length && !rivalAlcanzaBalon) {
				
				// Si en el ciclo calculado el jugador puede rematar el bal�n, calculamos si alcanza el bal�n.
				if ((sp.iteracionesParaRematarRival()[contador]-ciclos) <= 0) {
					
					rivalAlcanzaBalon = 
						futbolistaAlcanzaElBalon (sp.rivales()[contador], 
												  sp.detalleJugadoresRivales()[contador].getVelocidad(), 
												  balon, alturaBalon, ciclos,
												  (sp.detalleJugadoresRivales()[contador].esPortero() ? 
														  Constantes.DISTANCIA_CONTROL_BALON_PORTERO : Constantes.DISTANCIA_CONTROL_BALON),  
												  (sp.detalleJugadoresRivales()[contador].esPortero() ? 
														  Constantes.ALTO_ARCO : Constantes.ALTURA_CONTROL_BALON));
				}

				// Incrementamos el contador para buscar el siguiente rival.
				contador ++;
			}
		}

         return !rivalAlcanzaBalon;
    }
    
    
    private boolean porteroEnTrayectoria (SituacionPartido sp,
    									  Posicion origen,
    									  Posicion destino) {

    	// Calculamos la f�rmula de la recta de la trayectoria seg�n la ecuaci�n
    	// (y-y1)(x2-x1) = (y2-y1)(x-x1).
    	double A = -(destino.getY() - origen.getY());
    	double B = destino.getX() - origen.getX();
    	double C = -origen.getY() * (destino.getX() - origen.getX()) +
    	origen.getX() * (destino.getY() - origen.getY());

    	// Obtenemos el rival m�s cercano al gol (portero)
    	Posicion rival = sp.rivales()[destino.indiceMasCercano(sp.rivales())];
    	// Obtenemos la distancia del rival a la recta de la trayectoria seg�n la ecuaci�n
    	// |xP*A + yP*B + C| / (A^2 + B^2)^sqr.
    	double distancia = Math.abs((rival.getX() * A) + (rival.getY() * B) + C) /
    	Math.sqrt((A*A) + (B*B));

    	return distancia < Entorno.DISTANCIA_CORTE_TIRO;
    }
    
    

    
}